/**
 * 
 */
package org.statewalker.fsm.providers;

import java.util.List;

import org.statewalker.fsm.FsmProcess;
import org.statewalker.fsm.IFsmStateProvider;

/**
 * @author kotelnikov
 */
public abstract class AbstractFsmStateProvider<SK, S, EK, E>
    implements
    IFsmStateProvider<S, E> {

    private static int fCounter;

    private static int getId() {
        return fCounter++;
    }

    protected FsmTransitionsConst<SK, EK> fInfo;

    private int fInstanceId = getId();

    /**
     * 
     */
    protected AbstractFsmStateProvider(FsmTransitionsConst<SK, EK> info) {
        fInfo = info;
    }

    /**
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals(Object obj) {
        if (obj == this)
            return true;
        if (!(obj instanceof AbstractFsmStateProvider<?, ?, ?, ?>))
            return false;
        AbstractFsmStateProvider<?, ?, ?, ?> descriptor = (AbstractFsmStateProvider<?, ?, ?, ?>) obj;
        return fInstanceId == descriptor.fInstanceId;
    }

    protected abstract EK getEventKey(E event);

    /**
     * @see org.statewalker.fsm.IFsmStateProvider#getNextState(List<S>,
     *      java.lang.Object, java.lang.Object)
     */
    public final S getNextState(FsmProcess<S, E> process, S state, final E event) {
        List<S> stack = process.getStateStack();
        if (stack.isEmpty())
            return null;
        SK stateKey = getStateKey(state);
        if (stateKey == null)
            stateKey = fInfo.getInitialStateKey();
        SK targetKey = null;
        EK eventKey = getEventKey(event);
        while (eventKey != null) {
            targetKey = getNextSubstateKey(process, stateKey, eventKey);
            if (targetKey != null)
                break;
            eventKey = getParentEventKey(eventKey);
        }
        if (targetKey == null || targetKey.equals(fInfo.getFinalStateKey()))
            return null;
        return newState(process, targetKey);
    }

    /**
     * @param process
     * @param stateKey
     * @param eventKey
     * @return
     */
    protected SK getNextSubstateKey(
        FsmProcess<S, E> process,
        SK stateKey,
        EK eventKey) {
        SK targetKey = null;
        IFsmTransitions<SK, EK> transitions = getTransitions(process);
        if (transitions != null) {
            targetKey = transitions.getTarget(stateKey, eventKey);
            if (targetKey == null) {
                targetKey = transitions.getDefaultTarget(eventKey);
            }
        }
        return targetKey;
    }

    protected abstract EK getParentEventKey(EK eventKey);

    protected abstract SK getStateKey(S state);

    protected abstract IFsmTransitions<SK, EK> getTransitions(
        FsmProcess<S, E> process);

    /**
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode() {
        return toString().hashCode();
    }

    protected abstract S newState(FsmProcess<S, E> process, SK targetKey);

    /**
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        return getClass().getName() + "[" + fInstanceId + "]";
    }

}
